/*

    This file is part of Blue Violin.

    Blue Violin is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Blue Violin is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero Public License for more details.

    You should have received a copy of the GNU Affero Public License
    along with Blue Violin.  If not, see <http://www.gnu.org/licenses/>.

    Blue Violin  Copyright (C) 2008  Blue Violin, Inc. & Josh Watts <josh.watts@blueviolin.org>
	For more information, visit http://www.blueviolin.org/

    This program comes with ABSOLUTELY NO WARRANTY.

*/

function BVReplay()
{
	this.m_nHighlightCounter = 0;
	
	this.m_aPreviousAttributes = null;
	this.m_dtTestStart = null;
	this.m_dtTestEnd = null;
	this.m_dtStart = null;
	this.m_oReport = new BVReport();
	this.m_objBrowser = null;
	this.m_aBrowserStack = new Array();
}


BVReplay.prototype.GetAttributes = function(xmlAttributes)
{
	var aAttributes = new Object;
	if (null != xmlAttributes)
	{
		aAttributes = new Object;
		for (var i = 0;
			 i < xmlAttributes.length;
			 ++i)
		{
			aAttributes[xmlAttributes.item(i).nodeName] = xmlAttributes.item(i).value;
		}
	}		
	return aAttributes;	
};


BVReplay.prototype.FindElementEx = function(oAttributes)
{
	var docRoot;
	var docTemp;
	var aFrames = null;
	var aElements = null;
	var elemReturn = null;
	var elemTemp = null;
	var sDocPath = "";
	var winTemp;
	docRoot = this.m_objBrowser.Document;
//
// nice clean way of dealing with frames
//
	if ("document" != oAttributes["docPath"])
	{
		var aDocPathPieces = oAttributes["docPath"].split("::");
		winTemp = docRoot.parentWindow;
		for (var i = 0;
			 i < aDocPathPieces.length - 1;
			 ++i)
		{
			winTemp = winTemp.frames[aDocPathPieces[i]];
			docTemp = winTemp.document;
		}
		docRoot = docTemp;
	}
	aElements = docRoot.all;
	switch (oAttributes["tagName"].toUpperCase())
	{
		case "A":
			elemTemp = aElements[oAttributes["sourceIndex"]];
			if (null != elemTemp &&
				elemTemp.href == oAttributes["href"] &&
				elemTemp.innerText == oAttributes["innerText"])
			{
				elemReturn = elemTemp;
			}
			else if (null != elemTemp &&
					 elemTemp.innerText == oAttributes["innerText"])
			{
				elemReturn = elemTemp;
			}
			else
			{
				aElements = this.m_objBrowser.Document.getElementsByTagName("A");
				
				for (var i = 0;
					 i < aElements.length;
					 ++i)
				{
					elemTemp = aElements[i];
					if (elemTemp.href == oAttributes["href"] &&
						elemTemp.innerText == oAttributes["innerText"])
					{
						elemReturn = elemTemp;
						break;
					}
					else if (elemTemp.innerText.trim() == oAttributes["innerText"])
					{
						elemReturn = elemTemp;
						break;
					}
				}
			}
			break;

		case "BODY":
			elemReturn = docRoot.getElementsByTagName("body")[0];
			break;

		case "IMG":
			elemTemp = aElements[oAttributes["sourceIndex"]];
			if (null != elemTemp &&
				(elemTemp.src == oAttributes["src"] ||
				 elemTemp.id == oAttributes["id"]))
			{
				elemReturn = elemTemp;
			}
			else
			{
				for (var i = 0;
					 i < aElements.length;
					 ++i)
				{
					elemTemp = aElements[i];
					if (elemTemp.src == oAttributes["src"])
					{
						elemReturn = elemTemp;
						break;
					}
				}
			}
			break;
						

		case "INPUT":
			switch (oAttributes["type"].toUpperCase())
			{
				case "FILE":
					elemTemp = aElements[oAttributes["sourceIndex"]];
					if (null != elemTemp &&
						(elemTemp.id == oAttributes["id"] ||
						 elemTemp.name == oAttributes["name"]))
					{
						elemReturn = elemTemp;
					}
					else
					{
						for (var i = 0;
							 i < aElements.length;
							 ++i)
						{
							elemTemp = aElements[i];
							if (elemTemp.id == oAttributes["id"] ||
								elemTemp.name == oAttributes["name"])
							{
								elemReturn = elemTemp;
								break;
							}
						}
					}
					break;

				default:
					elemTemp = aElements[oAttributes["sourceIndex"]];
					if (null != elemTemp &&
						elemTemp.id == oAttributes["id"] &&
						elemTemp.name == oAttributes["name"])
					{
						elemReturn = elemTemp;
					}
					else
					{
						for (var i = 0;
							 i < aElements.length;
							 ++i)
						{
							elemTemp = aElements[i];
							if (elemTemp.id == oAttributes["id"] &&
								elemTemp.name == oAttributes["name"])
							{
								elemReturn = elemTemp;
								break;
							}
						}
					}
					break;
						
			}
			break;
				
		case "SELECT":
			elemTemp = aElements[oAttributes["sourceIndex"]];
			if (null != elemTemp &&
				(elemTemp.id == oAttributes["id"] ||
				 elemTemp.name == oAttributes["name"]))
			{
				elemReturn = elemTemp;
			}
			break;

		case "TEXTAREA":
			if (null != oAttributes["id"])
			{
				elemTemp = docRoot.getElementById(oAttributes["id"]);
			}
			if (null != elemTemp &&
				elemTemp.id == oAttributes["id"])
			{
				elemReturn = elemTemp;
			}
			else
			{
				elemTemp = aElements[oAttributes["sourceIndex"]];
				if (null != elemTemp &&
					elemTemp.id == oAttributes["id"])
				{
					elemReturn = elemTemp;
				}
			}
			break;


		default:

			if (null != oAttributes.id &&
				"" != oAttributes.id)
			{
				elemReturn = docRoot.getElementById(oAttributes.id);
			}
			else
			{
				elemTemp = aElements[oAttributes["sourceIndex"]];
				if (null != elemTemp &&
					elemTemp.tagName.toUpperCase() == oAttributes.tagName.toUpperCase() &&
					elemTemp.innerText == oAttributes.innerText)
				{
					elemReturn = elemTemp;
				}
				else
				{
					for (var i = 0;
						 i < aElements.length;
						 ++i)
					{
						elemTemp = aElements.item(i);
						if (elemTemp.tagName == oAttributes["tagName"] &&

							elemTemp.innerText == oAttributes["innerText"])
						{
							elemReturn = elemTemp;
							break;
						}
					}
				}
			}
			break;

	}
	return elemReturn;
};


BVReplay.prototype.HighlightStep = function(sNodeName, aAttributes)
{
	var oTree = document.getElementById("ulTree");
	var bHighlight = true;
	if (0 != this.m_nHighlightCounter)
	{
		oTree.childNodes(this.m_nHighlightCounter - 1).style.backgroundColor = "";
		oTree.childNodes(this.m_nHighlightCounter - 1).style.color = "black";
		if ("finishreplay" == sNodeName)
		{
			return;
		}
	}
	if (true == bHighlight)
	{
		oTree.childNodes(this.m_nHighlightCounter).style.backgroundColor = "#547aa8";
		oTree.childNodes(this.m_nHighlightCounter).style.color = "white";
		++this.m_nHighlightCounter;				
	}

};

BVReplay.prototype.HighlightException = function()
{
	var oTree = document.getElementById("ulTree");
	oTree.childNodes(this.m_nHighlightCounter - 1).style.backgroundColor = "#B01709";
	oTree.childNodes(this.m_nHighlightCounter - 1).style.color = "white";
}


BVReplay.prototype.DocumentStart = function(oDocument, sURL)
{
	this.m_dtStart = new Date();
};


BVReplay.prototype.scrollIntoView = function(elemSrc)
{
	// var nTop = elemSrc.
	var nScrollX = 0;
	var nScrollY = 0;
	elemSrc.scrollIntoView(true);
	if (20 >= elemSrc.clientTop)
	{
		nScrollY = -50;
	}
	if (20 >= elemSrc.clientLeft)
	{
		nScrollX = -50;
	}
	this.m_objBrowser.Document.parentWindow.scrollBy(nScrollX, nScrollY);
};


BVReplay.prototype.getScreenshot = function(oStep, elemSrc)
{
	var sBorder = null;
	if (null != elemSrc)
	{
		sBorder = elemSrc.style.border;
		this.scrollIntoView(elemSrc);
		elemSrc.style.border = "2px dashed red";
	}
	sUrl = document.URL;
	var nPos = sUrl.lastIndexOf("/");
	sUrl = sUrl.substring(0, nPos + 1) + "images.php";
// 	var sJson = this.m_objBrowser.ScreenCapture(false, sUrl);
// 	oStep.put_screenID(sJson.parseJSON(null).nImageID);	
};


BVReplay.prototype.changeBrowser = function(oAttributes)
{
	var oStep = null;

	this.m_objBrowser.Sleep(10);
	this.m_aBrowserStack.push(this.m_objBrowser);
	window.oAppEvents.SwitchActiveBrowser(document.getElementById(oAttributes["browser"].replace("obj", "spn")));
	this.m_objBrowser = document.getElementById(oAttributes["browser"]);

	oStep = this.m_oReport.addStep("changebrowser", oAttributes);
};


BVReplay.prototype.documentComplete = function(oAttributes)
{
	var dtStart = new Date();
	var dtEnd = null;
	var dtElapsed = null;
	var oStep = null;

	// cached pages will load immediately so we have to check
	this.m_objBrowser.Sleep(100);
	if (true != this.m_objBrowser.CheckDocumentComplete())
	{
		if ("documentcomplete" == this.m_aPreviousAttributes["action"])
		{
			if (true != this.m_objBrowser.CheckDocumentComplete())
			{
				while (true)
				{
					this.m_objBrowser.Sleep(100);
					if (true == this.m_objBrowser.CheckDocumentComplete())
					{
						break;
					}
				}
			}
		}
		else
		{
			this.m_objBrowser.WaitForDocumentComplete();
		}
	}
	dtEnd = new Date();
	dtElapsed = new Date(dtEnd.valueOf() - dtStart.valueOf())
	oAttributes["Elapsed"] = dtElapsed.format();
	oAttributes["URL"] = this.m_objBrowser.Document.URL;
	oStep = this.m_oReport.addStep("documentcomplete", oAttributes);
// 	this.getScreenshot(oStep, null);
};


BVReplay.prototype.documentSelect = function(oAttributes)
{
	var docRoot = this.m_objBrowser.Document;
	var reSearch = null;
	var ex = null;
	var oResults = null;
	var nRetries = 0;
		
	if ("document" != oAttributes.docPath)
	{
		var aDocPathPieces = oAttributes.docPath.split("::");
		winTemp = docRoot.parentWindow;
		for (var i = 0;
			 i < aDocPathPieces.length - 1;
			 ++i)
		{
			winTemp = winTemp.frames[aDocPathPieces[i]];
			docTemp = winTemp.document;
		}
		docRoot = docTemp;
	}

	nRetries = ("undefined" == typeof(oAttributes.retry)) ? 1 : oAttributes.retry ;

	nRetries = (nRetries <= 0) ? 1 : nRetries;

	reSearch = new RegExp(oAttributes.highlightedText);
	for (var i = 0;
		 i < nRetries;
		 ++i)
	{
		oResults = reSearch.exec(docRoot.body.innerText);
		if (null != oResults)
		{
			break;
		}
		this.m_objBrowser.Sleep(oAttributes.delay);
	}

	if (null == oResults)
	{
		ex = new DocumentSelectException("\"" + oAttributes.highlightedText + "\" was not found",
										 "documentselect");
		if ("" != oAttributes.failedMessage &&
			"undefined" !== typeof oAttributes.failedMessage)
		{
			ex.failedMessage = oAttributes.failedMessage;
		}
		throw ex;
	}
	else
	{
		oAttributes.found = oResults[0];
		this.m_oReport.addStep("documentselect", oAttributes);
	}
};


BVReplay.prototype.elementCheckpoint = function(oAttributes)
{
	var elemSrc = this.FindElementEx(oAttributes);
	var sMsg = null;
	var ex;

	if (null == elemSrc)
	{
		if (null != oAttributes.id ||
			"" != oAttributes.id)
		{
			sMsg = "Couldn't find " + oAttributes.id;
		}
		else if (null != oAttributes.name ||
				 "" != oAttributes.name)
		{
			sMsg = "Couldn't find " + oAttributes.name;
		}
		else
		{
			switch (oAttributes.tagName.toUpperCase())
			{
				case "A":
					sMsg = "Couldn't find link '" + oAttributes.innerText + "'";
					break;
					
				default:
					sMsg = "Couldn't find " + oAttributes.tagName.toUpperCase();
					break;
			}
		}
		ex = new Exception(sMsg);
		throw ex;
		
	}
	else
	{
		this.m_oReport.addStep("elementcheckpoint", oAttributes);
	}
	
};
	

BVReplay.prototype.click = function(oAttributes)
{
	var bReturnValue = true;
	var oStep = null;
	var sBorder = null;
	var evClick = null;
	var ex;

	elemSrc = this.FindElementEx(oAttributes);

	if (null == elemSrc)
	{
		switch (oAttributes["tagName"].toUpperCase())
		{
			case "IMG":
				ex = new ImageException("Couldn't find image \"" + oAttributes["id"] + "\"\n" + oAttributes["src"],
										"click");
				break;

			default:
				ex = new Exception("Couldn't find element \"" + oAttributes.tagName + "\"");
				break;
		}
		throw ex;
	}
	else
	{
		sBorder = elemSrc.style.border;
		elemSrc.style.border = "2px dashed red";

		switch (oAttributes["tagName"].toUpperCase())
		{
			case "A":
				//elemSrc = this.FindElementEx(oAttributes);
				if (null != elemSrc)
				{
					oAttributes["href"] = elemSrc.href;
					// oStep = this.m_oReport.addStep("click", oAttributes);
					// this.getScreenshot(oStep, elemSrc);
					elemSrc.click();
				}
				break;
							
			case "IMG":
				if (null != elemSrc)
				{
					elemSrc.click();
				}
				break;
							
			case "INPUT":
				switch (oAttributes["type"].toUpperCase())
				{
					case "SUBMIT":
						elemSrc.click();
						break;

					default:
						if (null != elemSrc)
						{
							evClick = elemSrc.document.createEventObject();
							evClick.button = oAttributes.button;
							evClick.offsetX = 5;
							evClick.offsetY = 5;
							
							elemSrc.fireEvent("onmousedown", evClick);
							elemSrc.fireEvent("onmouseup", evClick);
							elemSrc.fireEvent("onclick", evClick);
							elemSrc.fireEvent("onfocus", null);
						}
						break;
				}
				break;
							
			case "SELECT":
				break;

			default:
				if (null != elemSrc)
				{
					evClick = elemSrc.document.createEventObject();
					var bFired = false;
					evClick.button = oAttributes["button"];
					evClick.offsetX = oAttributes["offsetX"];
					evClick.offsetY = oAttributes["offsetY"];
					elemSrc.click();
					elemSrc.focus();
				}
				break;
		}

		elemSrc.style.border = sBorder;
		this.m_oReport.addStep("click", oAttributes);
	}
};


BVReplay.prototype.keyPress = function(oAttributes)
{

	var aKeyPressCodes = null;
	var evKeyPress = null;
	var elemSrc;
	var j = 0;

	switch (oAttributes["tagName"].toUpperCase())
	{
		case "DIV":
			elemSrc = this.FindElementEx(oAttributes);
			if (null != elemSrc)
			{
				aKeyPressCodes = oAttributes.keyCodeArray.split(",");
				for (j = 0;
					 j < aKeyPressCodes.length;
					 ++j)
				{
// 					SendKey(Number(aKeyPressCodes[j]),
// 							elemSrc,
// 							this.m_objBrowser);
					elemSrc.focus();
					this.m_objBrowser.SendKeys(String.fromCharCode(aKeyPressCodes[j]));
					this.m_objBrowser.Sleep(100);
				}
			}
			break;


		case "INPUT":
			switch (oAttributes["type"].toUpperCase())
			{
				case "FILE":
					elemSrc = this.FindElementEx(oAttributes);
					if (null != elemSrc)
					{

						// elemSrc.focus();
						//this.m_objBrowser.SendKeys(String.fromCharCode(oAttributes["keyCode"]));
					}
					break;
									

				case "IMAGE":
					elemSrc = this.FindElementEx(oAttributes);
					if (null != elemSrc)
					{
						var evKeyPress = elemSrc.document.createEventObject();
						var bFired = false;
						if (13 == oAttributes["keyCode"] ||
							32 == oAttributes["keyCode"])
						{
							elemSrc.click();
						}
						else
						{
							evKeyPress.keyCode = oAttributes["keyCode"];
							evKeyPress.cancelBubble = false;
							bFired = elemSrc.fireEvent("onkeydown", evKeyPress);
							bFired = elemSrc.fireEvent("onkeypress", evKeyPress);
						}
					}
					break;

				case "PASSWORD":
				case "TEXT":
					
					elemSrc = this.FindElementEx(oAttributes);
					if (null != elemSrc)
					{
						aKeyPressCodes = oAttributes.keyCodeArray.split(",");
						for (j = 0;
							 j < aKeyPressCodes.length;
							 ++j)
						{
							SendKey(Number(aKeyPressCodes[j]),
									elemSrc,
									this.m_objBrowser);
							this.m_objBrowser.Sleep(100);
						}
					}
					break;
			}
			break;
						
		case "TEXTAREA":
			elemSrc = this.FindElementEx(oAttributes);
			if (null != elemSrc)
			{
				aKeyPressCodes = oAttributes.keyCodeArray.split(",");
				for (j = 0;
					 j < aKeyPressCodes.length;
					 ++j)
				{
					SendKey(Number(aKeyPressCodes[j]),
							elemSrc,
							this.m_objBrowser);
					this.m_objBrowser.Sleep(50);
				}
			}
			break;

		default:
			elemSrc = this.FindElementEx(oAttributes);
			if (null != elemSrc)
			{
				var evKeyPress = elemSrc.document.createEventObject();
				var bFired = false;
				evKeyPress.keyCode = oAttributes["keyCode"];
				evKeyPress.cancelBubble = false;
				bFired = elemSrc.fireEvent("onkeydown", evKeyPress);
				bFired = elemSrc.fireEvent("onkeypress", evKeyPress);
			}
			break;
	}	
	this.m_oReport.addStep("keypress", oAttributes);

	function SendKey(nKeyCode, oElement, oBrowser)
	{
		var evKeyPress;

		if (32 <= nKeyCode &&
			126 >= nKeyCode)
		{
			evKeyPress = oElement.document.createEventObject();
			evKeyPress.altKey = false;
			evKeyPress.altLeft = false;
			evKeyPress.shiftKey = false;
			evKeyPress.shiftLeft = false;
			evKeyPress.ctrlKey = false;
			evKeyPress.ctrlLeft = false;
							
			evKeyPress.keyCode = nKeyCode;
							
			elemSrc.value += String.fromCharCode(nKeyCode);

			elemSrc.fireEvent("onkeydown", evKeyPress);
			elemSrc.fireEvent("onkeyup", evKeyPress);
			elemSrc.fireEvent("onkeypress", evKeyPress);
		}
		else
		{
			elemSrc.focus();
			oBrowser.SendKeys(String.fromCharCode(nKeyCode));
		}
	}


};


BVReplay.prototype.keyDown = function(oAttributes)
{
	var oEvent = null;
	var oElement = this.FindElementEx(oAttributes);

	// COM 
	// FALSE = 0
	// TRUE = -1

	if (null != oElement)
	{

		if (-1 == oAttributes.ctrlKey)
		{
			switch (String.fromCharCode(oAttributes.keyCode).toUpperCase())
			{
				case "A":
					var oTextRange = oElement.createTextRange();
					oTextRange.execCommand("SelectAll");
					break;
			}
		}

// 		oEvent = oElement.document.createEventObject();
// 		oEvent.ctrlKey = oAttributes.ctrlKey;
// 		oEvent.keyCode = oAttributes.keyCode;

// 		oElement.fireEvent("onkeydown", oEvent);
// 		oElement.fireEvent("onkeyup", oEvent);
	}
	return;
};


BVReplay.prototype.navigate = function(oAttributes)
{
	this.m_objBrowser.Navigate2(oAttributes["URL"]);
	this.m_oReport.addStep("navigate", oAttributes);
};


BVReplay.prototype.replay = function()
{
	var xmlEvents = window.oAppEvents.PrepareReplay();
	var xmlNodeList = xmlEvents.documentElement.childNodes;
	var elemSrc = null;
	var divPopupTabs = document.getElementById("divPopupTabs");
	var aChildren = divPopupTabs.children;
	var spnChild;
	var rePopup = /Popup/i;
	var nChildren = aChildren.length;
	var oAttributes = null;
	var elemTemp = null;
	var bReturn = null;

	this.m_nHighlightCounter = 0;
	this.m_aPreviousAttributes = null;
	$get("hdnReplay").value = "true";
	
	BVReport.hide();

	try
	{
		window.oAppEvents.ClearPopups();
		this.m_objBrowser = document.getElementById("objMainBrowser");
		this.m_objBrowser.attachEvent("DocumentStart", function() { this.DocumentStart });
		this.m_dtTestStart = new Date();
		this.m_oReport.start();
		for (var i = 0;
			 i < xmlNodeList.length;
			 ++i)
		{
			var xmlNode = xmlNodeList.item(i);
			var xmlAttributes = xmlNode.attributes;
			oAttributes = this.GetAttributes(xmlAttributes);
			oAttributes["action"] = xmlNode.nodeName;
			var oTree = document.getElementById("ulTree");

			this.HighlightStep(xmlNode.nodeName, oAttributes);

			if ("undefined" != typeof(oAttributes.delay))
			{
				this.m_objBrowser.Sleep(oAttributes.delay);
			}


			switch (xmlNode.nodeName)
			{
				case "back":
					this.m_objBrowser.GoBack();
					break;


				case "change":
					switch (oAttributes["tagName"].toUpperCase())
					{
						case "INPUT":
							switch (oAttributes["type"].toUpperCase())
							{
								case "FILE":
									var elemSrc = this.FindElementEx(oAttributes);
									if (null != elemSrc)
									{
										elemSrc.focus();
										this.m_objBrowser.SendKeys(oAttributes["value"]);
									}
									break;
							}
							break;
							
						case "SELECT":
							var elemSrc = this.FindElementEx(oAttributes);
							if (null != elemSrc)
							{
								var evClick = elemSrc.document.createEventObject();
								var bFired = false;
								elemSrc.selectedIndex = oAttributes["selectedIndex"];
								bFired = elemSrc.fireEvent("onchange", evClick);
							}
							break;
							
						default:
							break;
							
					}
					break;

				case "closewindow":
					window.oAppEvents.CloseWindow(oAttributes["browserID"]);
					this.m_objBrowser = this.m_aBrowserStack.pop();
					window.oAppEvents.SwitchActiveBrowser("spnMainBrowser");
					break;


				case "changebrowser":
					this.changeBrowser(oAttributes);

					break;
					
				case "click":
					bReturn = this.click(oAttributes);
					break;

				case "documentcomplete":
					this.documentComplete(oAttributes);
					break;

				case "documentselect":
					if (false == this.documentSelect(oAttributes))
					{
						alert("''" + oAttributes["highlitedText"] + "'' not found");
					
						return -1;
					}
					break;

				case "elementcheckpoint":
					this.elementCheckpoint(oAttributes);
					break;

				case "forward":
					this.m_objBrowser.GoForward();
					break;

				case "keydown":
					this.keyDown(oAttributes);
					break;

				case "keypress":
					this.keyPress(oAttributes);
					break;

				case "navigate":
					// this.m_objBrowser.Navigate2(oAttributes["URL"]);
					this.navigate(oAttributes);
					break;
			}
			if (false == bReturn)
			{
				break;
			}
			this.m_aPreviousAttributes = oAttributes;
			this.m_objBrowser.Sleep(100);
		}
		this.HighlightStep("finishreplay", null);
	}
	catch (ex)
	{
		this.m_oReport.addStep("exception", ex);
		this.HighlightException();
	}
	finally
	{
		this.m_dtTestEnd = new Date();
		var dtElapsed = new Date(this.m_dtTestEnd.valueOf() - this.m_dtTestStart.valueOf());
		window.oAppEvents.FinishReplay();
		this.m_oReport.Finish();
		this.m_oReport.display();
		$get("hdnReplay").value = "false";		
	}
};


